import collections

INDENT = '  '

class Node(object):
    def __init__(self, children=None, parent=None):
        self.children = children or []
        self.parent = parent

    def __str__(self):
        return self.serialize()

    def serialize(self, depth=None):
        raise NotImplementedError


class Class(Node):

    def __init__(self, name, parent=None, description=None, annotations=None, *args, **kwargs):
        super(Class, self).__init__(*args, **kwargs)
        self.name = name
        self.parent = parent
        self.description = description
        self.annotations = annotations or []

    def serialize(self, depth=None):
        depth = depth or 0

        string = ['/**\n']

        if self.description:
            string.append(' * {}\n'.format(self.description))

        for annotation in self.annotations:
            string.append(' * {}\n'.format(annotation))

        string.append(' * @constructor\n')
        string.append(' */\n')

        string.append('{} = function(){{\n'.format(self.name))

        for child in self.children:
            string.append(INDENT)
            string.append(child.serialize())
            string.append(';\n')

        string.append('}')

        return ''.join(string)

class ClosureBaseClass(Class):
    def __init__(self, *args, **kwargs):
        super(ClosureBaseClass, self).__init__(*args, **kwargs)

    def serialize(self, depth=None):
        string = super(ClosureBaseClass, self).serialize()
        string += ';\ngoog.inherits({}, {})'.format(self.name, self.parent)
        return string


class Method(Node):
    def __init__(self, name, arguments=None, parent=None, return_type=None, return_type_description=None, description=None, annotations=None, body=True, *args, **kwargs):
        super(Method, self).__init__(*args, **kwargs)
        self.name = name
        self.parent = parent
        self.arguments = arguments or []
        self.description = description or ''
        self.return_type = return_type
        self.return_type_description = return_type_description or ''
        self.annotations = annotations or []
        self.body = body

    def set_description(self, description):
        self.description = description

    def set_return_type(self, type, description=None):
        self.return_type = type
        self.return_type_description = description

    def serialize(self, depth=None):
        depth = depth or 0
        string = ['/**\n']

        # write the comment
        if self.description:
            string.append(' * {}\n'.format(self.description))

        for argument in self.arguments:
            if argument.type:
                string.append(' * @param {{{}}} {}\n'.format(argument.type, argument.name, argument.description))

        if self.return_type:
            string.append(' * @return {{{}}} {}\n'.format(self.return_type, self.return_type_description))

        for annotation in self.annotations:
            string.append(' * {}\n'.format(annotation))

        string.append(' */\n')

        # write the .prototype. assignment
        string.append(self.parent)
        string.append('.prototype.')
        string.append(self.name)

        if self.body:
            string.append(' = function(')

            args = []
            # serialize the argument list
            for argument in self.arguments:
                args.append(argument.serialize())

            string.append(', '.join(args))

            # build each argument value
            string.append('){\n')

            for child in self.children:
                string.append(child.serialize(depth+1))
                string.append(';\n')

            string.append('}')

        return ''.join(string)


class Property(Node):
    def __init__(self, name, parent=None, type=None, description=None, annotations=None,*args, **kwargs):
        super(Property, self).__init__(*args, **kwargs)
        self.name = name
        self.parent = parent
        self.description = description or ''
        self.type = type
        self.annotations = annotations or []


    def set_description(self, description):
        self.description = description

    def set_return_type(self, type, description=None):
        self.return_type = type
        self.return_type_description = description

    def serialize(self, depth=None):
        depth = depth or 0
        string = ['/**\n']

        # write the comment
        if self.description:
            string.append(' * {}\n'.format(self.description))

        if self.type:
            string.append(' * @type {{{}}}\n'.format(self.type))

        for annotation in self.annotations:
            string.append(' * {}\n'.format(annotation))

        string.append(' */\n')

        # write the .prototype. assignment
        string.append(self.parent)
        string.append('.prototype.')
        string.append(self.name)

        return ''.join(string)



class Argument(Node):
    def __init__(self, name, type=None, *args, **kwargs):
        super(Argument, self).__init__(*args, **kwargs)
        self.name = name
        self.type = type
        self.description = ''

    def set_type(self, type):
        self.type = type

    def set_description(self, description):
        self.description = description

    def serialize(self, depth=None):
        return str(self.name)


class Return(Node):
    def __init__(self, child):
        super(Return, self).__init__()
        self.child = child

    def serialize(self, depth=None):
        indent = INDENT * depth
        return '{}return {}'.format(indent, self.child.serialize())


class NewLine(Node):
    def serialize(self, depth=None):
        return "\n"


class Call(Node):
    def __init__(self, name, return_type=None, *args, **kwargs):
        super(Call, self).__init__(*args, **kwargs)
        self.name = name
        self.return_type = return_type

    def set_return_type(self, return_type):
        self.return_type = return_type

    def serialize(self, depth=None):
        depth = depth or 0
        indent = INDENT * depth


        string = [indent]

        if self.return_type:
            string.append('/** @type {{{}}} */ '.format(self.return_type))
            string.append('(')

        string.append(self.name)
        string.append('(')

        arg_string = []
        for arg in self.children:
            arg_string.append(arg.serialize(depth))
        string.append(', '.join(arg_string))
        string.append(')')

        if self.return_type:
            string.append(')')

        return ''.join(string)


class DictionaryLiteral(Node):
    def __init__(self):
        super(DictionaryLiteral, self).__init__()
        self.data = collections.OrderedDict()

    def set_key(self, name, value):
        self.data[name] = value

    def serialize(self, depth=None):
        depth = depth or 0
        indent = INDENT * depth
        string = ['{\n']

        items = []

        sub_indent = INDENT * (depth+1)
        for k, v in self.data.items():
            items.append('{}{}: {}'.format(sub_indent, k, v.serialize(depth+1) if isinstance(v, Node) else v))

        string.append(',\n'.join(items))

        string.append('\n{}}}'.format(indent))
        return ''.join(string)


class ClassProperty(Node):
    def __init__(self, name, value=None, type=None, description=None):
        super(ClassProperty, self).__init__()
        self.name = name
        self.value = value
        self.type = type
        self.description = description
        self.annotations = []

    def serialize(self, depth=None):
        depth = depth or 0
        string = []
        string.append('/**')

        if self.description:
            string.append(' * {}'.format(self.description))

        if self.type:
            string.append(' * @type {{{}}}\n'.format(self.type))

        for annotation in self.annotations:
            string.append(' * {}\n'.format(annotation))

        string.append(' */\n')
        string.append(self.name)

        if self.value is not None:
            string.append(" = {}".format(self.value.serialize(depth)))


        return ''.join(string)


class Literal(Node):
    def __init__(self, value):
        super(Literal, self).__init__()
        self.value = value

    def serialize(self, depth=None):
        return str(self.value)


class Assignment(Node):
    def __init__(self, name, value):
        super(Assignment, self).__init__()
        self.name = name
        self.value = value

    def serialize(self, depth=None):
        depth = depth or 0
        return "{} = {}".format(self.name, self.value.serialize(depth))


class Enum(Assignment):
    def __init__(self, name, value):
        super(Enum, self).__init__(name, value)

    def serialize(self, depth=None):
        depth = depth or 0
        return "/** @enum {{number}} */\n{} = {}".format(self.name, self.value.serialize(depth))


def quote_literal(string):
    return '"{}"'.format(string)


def upper_name(name):
    name = str(name)
    return ''.join(map(str.capitalize, name.split('_')))